/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package edu.yonsei.iwb.smarttv.model.measure;

import com.hp.hpl.jena.graph.Triple;
import com.hp.hpl.jena.rdf.model.*;
import com.hp.hpl.jena.vocabulary.OWL;
import com.hp.hpl.jena.vocabulary.RDF;
import com.hp.hpl.jena.vocabulary.RDFS;
import com.hp.hpl.jena.vocabulary.XSD;
import edu.yonsei.iwb.smarttv.util.SMARTTVLog;
import java.io.*;
import java.util.*;

/**
 *
 * @author Onlyjiny
 */
public class WeightModel {

    private static final String DUMMY_ANNOTATION = "http://iweb.yonsei.ac.kr/ontologies/smarttv/isDummy";
    private Model model;
    private ResourceWeights weight;
    private ResourceWeights instancesWeight;
    private UncountedResources ur;    
    private Hashtable<Property, PropertyWeightSet> weightTable;
    private ResourceWeights userPreferenceWeight;
    /**
     * 파일 기반의 Weight 모델 생성
     * @param model
     * @param type 
     */
    public WeightModel(Model model, String directory) {
        this.model = model;
        this.ur = new UncountedResources();
        this.weightTable = new Hashtable<Property, PropertyWeightSet>();
        this.weight = new ResourceWeights();
        this.instancesWeight = new ResourceWeights();        
        // File로부터 weightTable을 채울수 있는 메소드 추가.
        this.loadWeightTableFile(directory);
        this.initializeClassWeights();
        this.initializePropertyWeights(OWL.ObjectProperty);        
    }
    
        /**
     * 메모리 기반의 Weight 모델 생성
     * @param model 
     */
      public WeightModel(Model model){
        this.model = model;
        this.ur = new UncountedResources();
        this.weightTable = new Hashtable<Property, PropertyWeightSet>();
        this.weight = new ResourceWeights();
        this.instancesWeight = new ResourceWeights();
        this.initialize();
        this.writeWeightTableFile(WeightModelType.FILEADDRESS);
    }
        
    /**
     * 사용자 선호도 가중치 모델 생성자
     * @param pr 
     */
    public WeightModel(ResourceWeights pr, Hashtable<Property, PropertyWeightSet> pws){
        this.weightTable = pws;
        this.userPreferenceWeight = pr;
//        this.weight = new ResourceWeights();
//        this.instancesWeight = new ResourceWeights();        
    }
    
    public void updateWeightModelByUser(ResourceWeights pr, Hashtable<Property, PropertyWeightSet> pws){
        this.weightTable = pws;
        this.userPreferenceWeight = pr;
    }

    private void initialize() {
        this.initializeClassWeights();
        this.initializePropertyWeights(OWL.ObjectProperty);
        this.initializeTripleWeights(OWL.ObjectProperty);
        this.updateWeightTableByPropertyWeight();
        this.addSubClassOfWeightValue();
    }    

    private void addSubClassOfWeightValue() {
        StmtIterator iter = this.model.listStatements(null, RDFS.subClassOf, (RDFNode) null);
        PropertyWeightSet pws = new PropertyWeightSet();
        while (iter.hasNext()) {
            Statement st = iter.nextStatement();
            
            Resource subject = st.getSubject();
            Resource object = st.getObject().asResource();
            if(object.getNameSpace().equals(XSD.getURI()) || object.getNameSpace().equals(RDFS.getURI())){                
                continue;
            }            
            pws.add(new PropertyWeight(subject, RDFS.subClassOf, object, 1.0));
        }
        weightTable.put(RDFS.subClassOf, pws);
        Normalization.normalizeByMinMax(weightTable);
    }

    private void updateWeightTableByPropertyWeight() {
        Enumeration<Property> e = this.weightTable.keys();
        while (e.hasMoreElements()) {
            Property key = e.nextElement();
            double pweight = this.getWeight(key);
            PropertyWeightSet pws = this.weightTable.get(key);
            for (int i = 0; i < pws.size(); i++) {
                double tweight = pws.get(i).getWeight();
                double mergedweight = (pweight * 0.5) + (tweight * 0.5);
                pws.get(i).setWeight(mergedweight);
            }
        }
    }

    private void initializeClassWeights() {
        Vector<Resource> dummyClasses = new Vector<Resource>();
        ResIterator dummies = this.model.listResourcesWithProperty(ResourceFactory.createProperty(WeightModel.DUMMY_ANNOTATION), true);
        while (dummies.hasNext()) {
            Resource dummy = dummies.nextResource();
            dummyClasses.add(dummy);
        }
        Vector<Resource> empty = new Vector<Resource>();
        int countOfInstances = 0;
        ResourceWeights tempClass = new ResourceWeights();
        ResIterator ri = this.model.listSubjectsWithProperty(RDF.type, OWL.Class);
        while (ri.hasNext()) {
            Resource classResource = ri.next();
            if (!dummyClasses.contains(classResource) && classResource.isURIResource()) {
                ResIterator instancesOfClass = this.model.listSubjectsWithProperty(RDF.type, classResource);
                int count = this.count(instancesOfClass);
                if (count == 0) {
                    empty.add(classResource);
                } else {
                    countOfInstances += count;
                    tempClass.put(classResource, (double) count);
                }
            }
        }
        this.update(tempClass, countOfInstances);
        for (int i = 0; i < dummyClasses.size(); i++) {
            this.weight.put(dummyClasses.get(i), 0.5);
        }
        this.updateEmptySet(empty);
        this.initializeInstancesWeights(tempClass);
    }

    private void initializeInstancesWeights(ResourceWeights tempClass) {
        int countOfInstances = 0;
        ResourceWeights tempInstances = new ResourceWeights();
        Vector<Resource> instances = new Vector<Resource>();
        for (int i = 0; i < this.ur.size(); i++) {
            Resource r = this.ur.get(i);
            if (tempClass.containsKey(r)) {
                tempClass.remove(this.ur.get(i));
            }
        }
        Enumeration<Resource> keys = tempClass.keys();
        while (keys.hasMoreElements()) {
            ResIterator instancesOfClass = this.model.listSubjectsWithProperty(RDF.type, keys.nextElement());
            while (instancesOfClass.hasNext()) {
                Resource instance = instancesOfClass.nextResource();
                if (!instances.contains(instance)) {
                    instances.add(instance);
                }
            }
        }
        for (int i = 0; i < instances.size(); i++) {
            Resource instance = instances.get(i);
            StmtIterator triplesAsSubject = this.model.listStatements(instance, null, (RDFNode) null);
            int count = this.count(triplesAsSubject);
            StmtIterator triplesAsObject = this.model.listStatements(null, null, (RDFNode) instance);
            count += this.count(triplesAsObject);
            countOfInstances += count;
            tempInstances.put(instance, (double) count);
        }
        keys = tempInstances.keys();
        while (keys.hasMoreElements()) {
            Resource key = keys.nextElement();
            double value = tempInstances.get(key);
            tempInstances.put(key, 1 - (value / countOfInstances));
        }
        this.instancesWeight.putAll(Normalization.normalizeByMinMax(tempInstances));
        
    }

    private void initializeTripleWeights(Resource targetProperty) {

        ResIterator ri = this.model.listSubjectsWithProperty(RDF.type, targetProperty);
        Resource prop = null;
        while (ri.hasNext()) {
            prop = ri.next();
            Property property = ResourceFactory.createProperty(prop.getURI());

            PropertyWeightSet pws = new PropertyWeightSet();
            NodeIterator ni1 = this.model.listObjectsOfProperty(prop, RDFS.domain);

            while (ni1.hasNext()) {
                Resource domain = ni1.nextNode().asResource();

                NodeIterator ni2 = this.model.listObjectsOfProperty(prop, RDFS.range);
                while (ni2.hasNext()) {
                    Resource range = ni2.nextNode().asResource();
                    double w = this.getTripleWeightOfProperty(domain, property, range);
//                    System.out.println(domain.getLocalName() + "  " + property.getLocalName() + "  " + range.getLocalName() + " === " + w);
                    pws.add(new PropertyWeight(domain, property, range, w));


                    ResIterator ri2 = this.model.listResourcesWithProperty(RDFS.subClassOf, range);
                    while (ri2.hasNext()) {
                        // range의 subclass들로 향하는 방향
                        Resource subclassOfr = ri2.nextResource();
                        if (!subclassOfr.equals(range)) {
                            double w1 = this.getTripleWeightOfProperty(domain, property, subclassOfr);
//                            System.out.println(domain.getLocalName() + "  " + property.getLocalName() + "  " + subclassOfr.getLocalName() + " === " + w1);
                            pws.add(new PropertyWeight(domain, property, subclassOfr, w1));
                        }
                    }

                    ResIterator ri1 = this.model.listResourcesWithProperty(RDFS.subClassOf, domain);
                    while (ri1.hasNext()) {
                        Resource subclassOfd = ri1.nextResource();
                        if (!subclassOfd.equals(domain)) {
                            ResIterator ri3 = this.model.listResourcesWithProperty(RDFS.subClassOf, range);
                            while (ri3.hasNext()) {
                                // subclass 간의 관계
                                Resource subclassOfr = ri3.nextResource();
                                double w1 = this.getTripleWeightOfProperty(subclassOfd, property, subclassOfr);
//                                System.out.println(subclassOfd.getLocalName() + "  " + property.getLocalName() + "  " + subclassOfr.getLocalName() + " === " + w1);
                                pws.add(new PropertyWeight(subclassOfd, property, subclassOfr, w1));
                            }
                        }
                    }
                }
            }
            weightTable.put(property, pws);
        }
        Normalization.normalizeByMinMax(weightTable);
    }

    private double getTripleWeightOfProperty(Resource domain, Property property, Resource range) {

        double numerator = 0;
        double denominator = 0;

        ResIterator res1 = this.model.listResourcesWithProperty(RDF.type, domain);
        while (res1.hasNext()) {
            Resource dr = res1.nextResource();
            ResIterator res2 = this.model.listResourcesWithProperty(RDF.type, range);
            while (res2.hasNext()) {
                Resource rr = res2.nextResource();

                StmtIterator stmt = this.model.listStatements(dr, property, rr);
                StmtIterator stmt2 = this.model.listStatements(dr, null, rr);
                
                while (stmt.hasNext()) {
                    stmt.nextStatement();
                    numerator++;
                }
                while (stmt2.hasNext()) {
                    stmt2.nextStatement();
                    denominator++;
                }
            }
        }

        if (denominator == 0 || numerator == 0) {
            return 1.0;
        }

        double w = numerator / denominator;
        return 1.0 - w;
    }

    public Hashtable<Property, PropertyWeightSet> getTripleWeightTable() {
        return this.weightTable;
    }

    private void initializePropertyWeights(Resource targetProperty) {
        Vector<Resource> empty = new Vector<Resource>();
        int countOfProperties = 0;
        ResourceWeights tempProperty = new ResourceWeights();
        ResIterator ri = this.model.listSubjectsWithProperty(RDF.type, targetProperty);
        while (ri.hasNext()) {
            Resource property = ri.next();
            ResIterator tripleOfProperty = this.model.listResourcesWithProperty(ResourceFactory.createProperty(property.getURI()));
            int count = this.count(tripleOfProperty);
            if (count == 0) {
                empty.add(property);
            } else {
                countOfProperties += count;
                tempProperty.put(property, (double) count);
            }
        }
        this.update(tempProperty, countOfProperties);
        this.updateEmptySet(empty);
    }

    private void update(ResourceWeights weights, int totalCount) {
        for (int i = 0; i < this.ur.size(); i++) {
            Resource r = this.ur.get(i);
            if (weights.containsKey(r)) {
                double count = weights.get(r);
                totalCount -= count;
                weights.remove(ur.get(i));
            }
        }
        ResourceWeights temp = new ResourceWeights();
        Enumeration<Resource> keys = weights.keys();
        while (keys.hasMoreElements()) {
            Resource key = keys.nextElement();
            double value = weights.get(key);
            temp.put(key, 1 - (value / totalCount));
        }
        this.weight.putAll(Normalization.normalizeByMinMax(temp));
    }

    private void updateEmptySet(@SuppressWarnings("UseOfObsoleteCollectionType") Vector<Resource> empty) {
        for (int i = 0; i < empty.size(); i++) {
            this.weight.put(empty.get(i), 1.0);
        }
    }

    private int count(ResIterator ri) {
        int result = 0;
        while (ri.hasNext()) {
            result++;
            ri.next();
        }
        return result;
    }

    private int count(StmtIterator si) {
        int result = 0;
        while (si.hasNext()) {
            result++;
            si.next();
        }
        return result;
    }

    public double getWeight(Resource head, Resource arc, Resource tail) {
        double result = 0.0;
        result = this.weight.get(head) + this.weight.get(arc) + this.weight.get(tail);
        if (result == 0.0) {
            return 1;
        } else {
            return result;
        }
    }
    
    public double getUserPreferenceWeight(Resource r){
        if (!this.userPreferenceWeight.containsKey(r)) {
            return 1.0;
        }
        double result = 0.0;
        result = this.userPreferenceWeight.get(r);
        if (result == 0.0) {
            return 1.0;
        } else {
            return result;
        }        
    }

    public double getWeight(Resource arc) {
        if (!this.weight.containsKey(arc)) {
            return 1.0;
        }
        double result = 0.0;
        result = this.weight.get(arc);
        if (result == 0.0) {
            return 1.0;
        } else {
            return result;
        }
    }

    public double getWeight(Triple triple) {
        double result = 1.0;
        Property p = this.model.createProperty(triple.getPredicate().getURI());

        PropertyWeightSet pws = this.weightTable.get(p);

        for (int i = 0; i < pws.size(); i++) {
            PropertyWeight pw = pws.get(i);
            if (pw.getDomain().equals(this.model.createProperty(triple.getSubject().getURI()))) {
                if (pw.getRange().equals(this.model.createProperty(triple.getObject().getURI()))) {
                    return pw.getWeight();
                }
            }
        }

        return result;
    }
    
    
    public ResourceWeights getOriginalOntologyWeight () {
        ResourceWeights originalWeight = new ResourceWeights();
        originalWeight.putAll(this.weight);
        originalWeight.putAll(this.instancesWeight);
        return originalWeight;
    }
    
    public ResourceWeights getUserPreferenceResourceWeights(){
        return this.userPreferenceWeight;
    }
        

    public double getInstanceWeight(Resource r) {
        if (!this.instancesWeight.containsKey(r)) {
            return 1.0;
        }
        double result = 0.0;
        result = this.instancesWeight.get(r);
        if (result == 0.0) {
            return 1;
        } else {
            return result;
        }
    }
    
    private void writeWeightTableFile(String filename) {
        try {
            File file = new File(filename);
            file.createNewFile();
            FileOutputStream fos = new FileOutputStream(file);
            BufferedOutputStream bos = new BufferedOutputStream(fos);
            PrintWriter pw = new PrintWriter(bos);
            
            Enumeration<Property> e = this.weightTable.keys();
            while (e.hasMoreElements()) {
                Property key = e.nextElement();
                PropertyWeightSet pws = this.weightTable.get(key);
                for (int i = 0; i < pws.size(); i++) {
                    pw.write(pws.get(i).getDomain() + "," + pws.get(i).getProperty() + "," + pws.get(i).getRange() + "," + pws.get(i).getWeight());
                    pw.println();
                }
            }

            pw.flush();

        } catch (IOException ex) {
            ex.printStackTrace();
        }
    }

    private void loadWeightTableFile(String directory) {
        
        FileReader fr;
        BufferedReader br;
        String getLine;

        try {
            SMARTTVLog.log(SMARTTVLog.DETAIL, "Load Weight Table from File \n");

            fr = new FileReader(directory);
            br = new BufferedReader(fr);
            while ((getLine = br.readLine()) != null) {

                StringTokenizer st = new StringTokenizer(getLine, ",");
                Resource domain = null;
                Property property = null;
                Resource range = null;
                double w = 0;
                for (int i = 1; st.hasMoreElements(); i++) {
                    if (i == 1) {
                        domain = ResourceFactory.createResource(st.nextToken());
                    } else if (i == 2) {
                        property = ResourceFactory.createProperty(st.nextToken());
                    } else if (i == 3) {
                        range = ResourceFactory.createResource(st.nextToken());
                    } else if (i == 4) {
                        w = Double.valueOf(st.nextToken());
                    }
                }
                if (weightTable.containsKey(property)) {
                    PropertyWeightSet pws = weightTable.get(property);
                    pws.add(new PropertyWeight(domain, property, range, w));
                } else {
                    PropertyWeightSet pws = new PropertyWeightSet();
                    pws.add(new PropertyWeight(domain, property, range, w));
                    weightTable.put(property, pws);
                }
            }
            br.close();
            fr.close();

        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer();
        sb.append("::::: Weights of classes and properties\n");
        Enumeration<Resource> keys = this.weight.keys();
        while (keys.hasMoreElements()) {
            Resource key = keys.nextElement();
            sb.append(key.getURI() + ": " + this.weight.get(key) + "\n");
        }
        sb.append("::::: Weights of instances\n");
        keys = this.instancesWeight.keys();
        while (keys.hasMoreElements()) {
            Resource key = keys.nextElement();
            sb.append(key.getURI() + ": " + this.instancesWeight.get(key) + "\n");
        }
        return sb.toString();
    }
}
